﻿using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;

namespace Asdo.Controls
{
    public class WindowSupporter : ContentControl
    {
        static WindowSupporter()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(WindowSupporter), new FrameworkPropertyMetadata(typeof(WindowSupporter)));

            MinimizeCommand = new RoutedUICommand("Minimize", "Minimize", typeof(WindowSupporter));
            MaximizeCommand = new RoutedUICommand("Maximize", "Maximize", typeof(WindowSupporter));

            CommandManager.RegisterClassCommandBinding(typeof(UIElement), new CommandBinding(MinimizeCommand, OnMinimizeCommandExecuted));
            CommandManager.RegisterClassCommandBinding(typeof(UIElement), new CommandBinding(MaximizeCommand, OnMaximizeCommandExecuted));
            CommandManager.RegisterClassCommandBinding(typeof(UIElement), new CommandBinding(ApplicationCommands.Close, OnCloseCommandExecuted));

        }

        private static void OnCloseCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            var win = e.Parameter as Window;
            if (win != null)
            {
                win.Close();
            }
        }

        private static void OnMaximizeCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            var win = e.Parameter as Window;
            if (win != null)
            {
                MaximizeImpl(win);
            }
        }

        private static void OnMinimizeCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            var win = e.Parameter as Window;
            if (win != null)
            {
                win.WindowState = WindowState.Minimized;
            }
        }

        private static void MaximizeImpl(Window win)
        {
            win.WindowState = win.WindowState != WindowState.Maximized ?
                WindowState.Maximized : WindowState.Normal;
        }

        #region Commands

        private static readonly RoutedUICommand MinimizeCommand;
        private static readonly RoutedUICommand MaximizeCommand;

        public static RoutedUICommand Minimize
        {
            get { return MinimizeCommand; }
        }

        public static RoutedUICommand Maximize
        {
            get { return MaximizeCommand; }
        }

        #endregion


        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);

            Window win = null;

            for (var p = LogicalTreeHelper.GetParent(this); p != null && win == null; p = LogicalTreeHelper.GetParent(p))
            {
                win = p as Window;
            }

            if (win != null)
            {
                win.AddHandler(MouseLeftButtonDownEvent, new MouseButtonEventHandler(MouseLeftButtonDownHandler));
                win.AddHandler(MouseLeftButtonUpEvent, new MouseButtonEventHandler(MouseLeftButtonUpHandler));
                win.AddHandler(MouseMoveEvent, new MouseEventHandler(MouseMoveHandler));
                win.SourceInitialized += OnWinSourceInitialized;
            }
        }

        private static void OnWinSourceInitialized(object sender, EventArgs e)
        {
            var win = (Window)sender;
            win.SourceInitialized -= OnWinSourceInitialized;
            if (win.WindowStyle == WindowStyle.None)
            {
                AssignWindow(win);
            }
        }

        #region Win32Interop

        // ReSharper disable InconsistentNaming
        // ReSharper disable FieldCanBeMadeReadOnly.Local
        // ReSharper disable MemberCanBePrivate.Local
        private static void AssignWindow(Window window)
        {
            IntPtr handle = (new WindowInteropHelper(window)).Handle;
            var hwnd = HwndSource.FromHwnd(handle);
            if (hwnd != null)
            {
                hwnd.AddHook(WindowProc);
            }
        }

        private static IntPtr WindowProc(
            IntPtr hwnd,
            int msg,
            IntPtr wParam,
            IntPtr lParam,
            ref bool handled)
        {
            switch (msg)
            {
                case 0x0024:
                    WmGetMinMaxInfo(hwnd, lParam);
                    handled = true;
                    break;
            }

            return (IntPtr)0;
        }

        private static void WmGetMinMaxInfo(IntPtr hwnd, IntPtr lParam)
        {
            var mmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));

            // Adjust the maximized size and position to fit the work area of the correct monitor
            // ReSharper disable ConvertToConstant.Local
            int MONITOR_DEFAULTTONEAREST = 0x00000002;
            // ReSharper restore ConvertToConstant.Local
            IntPtr monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);

            if (monitor != IntPtr.Zero)
            {
                var monitorInfo = new MONITORINFO();
                GetMonitorInfo(monitor, monitorInfo);
                RECT rcWorkArea = monitorInfo.rcWork;
                RECT rcMonitorArea = monitorInfo.rcMonitor;
                mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left);
                mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top);
                mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left);
                mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top);
            }

            Marshal.StructureToPtr(mmi, lParam, true);
        }

        [DllImport("user32")]
        private  static extern bool GetMonitorInfo(IntPtr hMonitor, MONITORINFO lpmi);
        [DllImport("User32")]
        private  static extern IntPtr MonitorFromWindow(IntPtr handle, int flags);

        [StructLayout(LayoutKind.Sequential)]
        private  struct MINMAXINFO
        {
            public POINT ptReserved;
            public POINT ptMaxSize;
            public POINT ptMaxPosition;
            public POINT ptMinTrackSize;
            public POINT ptMaxTrackSize;
        } ;

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        private class MONITORINFO
        {
            public int cbSize = Marshal.SizeOf(typeof(MONITORINFO));
            public RECT rcMonitor;
            public RECT rcWork;
            public int dwFlags;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct POINT
        {
            public int x;
            public int y;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 0)]
        private struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;
        }
        // ReSharper restore MemberCanBePrivate.Local
        // ReSharper restore InconsistentNaming
        // ReSharper restore FieldCanBeMadeReadOnly.Local

        #endregion



        public static bool GetMoveWindow(DependencyObject obj)
        {
            return (bool)obj.GetValue(MoveWindowProperty);
        }

        public static void SetMoveWindow(DependencyObject obj, bool value)
        {
            obj.SetValue(MoveWindowProperty, value);
        }

        // Using a DependencyProperty as the backing store for MoveWindow.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MoveWindowProperty =
            DependencyProperty.RegisterAttached("MoveWindow", typeof(bool), typeof(WindowSupporter), new FrameworkPropertyMetadata(false));
        
        private void MouseLeftButtonDownHandler(object sender, MouseButtonEventArgs e)
        {
            var win = e.Source as Window;

            if (win == null )
            {
                var d = (FrameworkElement) e.Source;

                while (d != null && !GetMoveWindow(d))
                {
                    d = (FrameworkElement) d.Parent;
                }

                if (d != null)
                {
                    win = sender as Window;
                }
            }

            if (win != null)
            {
                if (e.ClickCount == 2)
                {
                    MaximizeImpl(win);
                }
                else
                {
                    var pos = e.GetPosition(win);
                    
                    _isWiden = CalcDirection(pos, win.Width, win.Height);

                    if (_isWiden == Direction.None)
                    {
                        win.DragMove();
                    }
                }
            }
        }

        private Direction _isWiden = Direction.None;

        private void MouseMoveHandler(object sender, MouseEventArgs e)
        {
            var win = e.Source as Window;


            if (win == null)
            {
                win = sender as Window;
                if (win != null)
                {
                    win.Cursor = null;
                }
            }
            else
            {
                var pos = e.GetPosition(win);
                var direction = win.WindowState == WindowState.Normal && win.ResizeMode != ResizeMode.NoResize
                                    ? CalcDirection(pos, win.Width, win.Height)
                                    : Direction.None;

                Cursor cur = null;

                switch (_isWiden | direction)
                {
                    case Direction.N:
                    case Direction.S:
                        cur = Cursors.SizeNS;
                        break;
                    case Direction.W:
                    case Direction.E:
                        cur = Cursors.SizeWE;
                        break;
                    case Direction.NE:
                    case Direction.SW:
                        cur = Cursors.SizeNESW;
                        break;
                    case Direction.NW:
                    case Direction.SE:
                        cur = Cursors.SizeNWSE;
                        break;
                }

                win.Cursor = cur;

                if (_isWiden != Direction.None)
                {
                    win.CaptureMouse();
                    if (TestDir(_isWiden, Direction.E))
                    {
                        double newWidth = e.GetPosition(win).X + Margin.Right;
                        Debug.WriteLine(newWidth);
                        if (newWidth > 0)
                        {
                            win.Width = newWidth;
                        }
                    }
                    if (TestDir(_isWiden, Direction.S))
                    {
                        double newWidth = e.GetPosition(win).Y + Margin.Bottom;
                        Debug.WriteLine(newWidth);
                        if (newWidth > 0)
                        {
                            win.Height = newWidth;
                        }
                    }
                    if (TestDir(_isWiden, Direction.W))
                    {
                        var p = e.GetPosition(win);
                        var v = p.X;
                        if (win.Width > v)
                        {
                            win.Left += p.X;
                            win.Width -= v;
                        }
                    }
                    if (TestDir(_isWiden, Direction.N))
                    {
                        var p = e.GetPosition(win);
                        var v = p.Y;
                        if (win.Height > v)
                        {
                            win.Top += p.Y;
                            win.Height -= v;
                        }
                    }
                }
            }
        }

        private static bool TestDir(Direction variable, Direction value)
        {
            return (variable & value) == value;
        }

        private void MouseLeftButtonUpHandler(object sender, MouseEventArgs e)
        {
            _isWiden = Direction.None;

            var win = sender as Window;
            if (win != null)
            {
                win.ReleaseMouseCapture();
            }
        }

        private Direction CalcDirection(Point pos, double width, double height)
        {
            var res = Direction.None;

            if (pos.Y <= Margin.Top)
            {
                res |= Direction.N;
            }
            if (pos.X <= Margin.Left)
            {
                res |= Direction.W;
            }

            if (pos.Y >= height - Margin.Bottom)
            {
                res |= Direction.S;
            }
            if (pos.X >= width - Margin.Right)
            {
                res |= Direction.E;
            }
            
            return res;
        }

        [Flags]
        private enum Direction
        {
            None = 0x0,
            N = 0x01,   // 0001
            S = 0x02,   // 0010
            E = 0x04,   // 0100
            W = 0x08,   // 1000
            NE = N | E, // 0101
            NW = N | W, // 1001
            SE = S | E, // 0110
            SW = S | W, // 1010
        }
    }
}
